export class BezierUtils {
    /**
     * 获取当前所在贝塞尔曲线段的信息
     * @param points 连续三阶贝塞尔曲线点数组
     * @param lengthPassed 在当前连续曲线上已经走过的长度
     * @returns { index: 第几段, passed: 当前段已走过长度, points: 当前段的4个点 }
     */
    public static getCurSegInfo(points: Point[], lengthPassed: number) {
        // 分段长度数组
        const { array: lineLengthArray } = this.getLineLengthArray(points);
        return this.getCurSegInfoWithLLA(points, lineLengthArray, lengthPassed);
    }
    /**
     * 获取当前所在贝塞尔曲线段的信息(需传入分段长度，以减小每次运算量)
     * @param points 连续三阶贝塞尔曲线点数组
     * @param lengthPassed 在当前连续曲线上已经走过的长度
     * @returns { index: 第几段, passed: 当前段已走过长度, points: 当前段的4个点 }
     */
    public static getCurSegInfoWithLLA(points: Point[], lineLengthArray: number[], lengthPassed: number) {
        // 总段数
        const totalSegments = lineLengthArray.length;
        // 分段索引值
        let lineSegmentIndex = 0;
        let lengthPassedTemp = lengthPassed;
        // 找到当前位于哪一段曲线
        for (; lineSegmentIndex < totalSegments; ++lineSegmentIndex) {
            lengthPassedTemp -= lineLengthArray[lineSegmentIndex];
            if (lengthPassedTemp < 0) break;
        }
        const curSegmentPassedLength = lengthPassedTemp + lineLengthArray[lineSegmentIndex];
        if (lineSegmentIndex >= totalSegments) {
            const segIndexTemp = lineSegmentIndex - 1;
            return {
                index: segIndexTemp,
                passed: lineLengthArray[segIndexTemp],
                points: points.slice(segIndexTemp * 3, segIndexTemp * 3 + 4) as PointTuple3
            }
        } else {
            return {
                index: lineSegmentIndex,
                passed: curSegmentPassedLength,
                points: points.slice(lineSegmentIndex * 3, lineSegmentIndex * 3 + 4) as PointTuple3
            };
        }
    }
    /**
     * 长度转换为实际百分比
     * @param p 三阶贝塞尔曲线的一段上的四个点
     * @param length 实际运行的长度
     * @returns 运行百分比
     */
    public static t2rt(p: PointTuple3, length: number) {
        let realTime = 0, rtLength = 0, deltaLength = 0, deltaTime = 0, low = 0, high = 1;
        do {
            if (deltaLength > 0) {
                deltaTime = realTime - low;
                realTime -= deltaTime / 2;
            } else {
                deltaTime = high - realTime;
                realTime += deltaTime / 2;
            }
            // 计算弧长差值
            rtLength = this.getSegmentLength(p, realTime);
            deltaLength = rtLength - length;
            // 更新二分上下限
            if (deltaLength > 0) high = realTime;
            else low = realTime;
        } while (Math.abs(deltaLength) > 0.05 && deltaTime > 0.00000001);

        return realTime;
    }
    /**
     * 获取连续贝塞尔曲线每一段的长度
     * @param points 贝塞尔点位
     * @returns 每一段曲线的近似长度
     */
    public static getLineLengthArray(points: Point[]) {
        const lineLengthArray: number[] = [];
        if (points.length > 0) {
            for (let i = 0; i < points.length - 3; i += 3) {
                const p0 = points[i];
                const c0 = points[i + 1];
                const c1 = points[i + 2];
                const p1 = points[i + 3];
                const pArray: PointTuple3 = [{ x: p0.x, y: p0.y }, { x: c0.x, y: c0.y }, { x: c1.x, y: c1.y }, { x: p1.x, y: p1.y }];
                const lineLength = this.getSegmentLength(pArray, 1);
                lineLengthArray.push(lineLength);
            }
        }
        const totalLineLength = lineLengthArray.reduce((pre, cur) => pre + cur, 0);
        return {
            total: totalLineLength,
            array: lineLengthArray
        };
    }
    /**
     * 获取连续贝塞尔曲线其中一段的长度
     * @param p 三阶贝塞尔曲线的一段上的四个点
     * @param t 运行百分比
     * @returns 曲线近似长度
     */
    public static getSegmentLength(p: PointTuple3, t: number) {
        const TotalSimpsonStep = 400;
        let stepCount = Math.floor(TotalSimpsonStep * t);
        if (stepCount === 0) return 0;
        if (stepCount % 2 === 0) stepCount++;
        const halfCount = Math.floor(stepCount / 2);
        let deltaStep = t / stepCount;
        let i = 0, sum1 = 0, sum2 = 0;
        while (i < halfCount) {
            sum1 += this.getSpeed(p, (2 * i + 1) * deltaStep);
            i++;
        }
        i = 1;
        while (i < halfCount) {
            sum2 += this.getSpeed(p, 2 * i * deltaStep);
            i++;
        }
        return (this.getSpeed(p, 0) + this.getSpeed(p, 1) + 4 * sum1 + 2 * sum2) * deltaStep / 3;
    }
    /**
     * 获取当前速度值
     * @param p 三阶贝塞尔曲线的一段上的四个点
     * @param t 运行百分比
     * @returns 曲线速度(数值)
     */
    public static getSpeed(p: PointTuple3, t: number) {
        const { vx, vy } = this.getVelocity(p, t);
        return Math.sqrt(vx * vx + vy * vy);
    }
    /**
     * 获取当前速度向量
     * @param p 三阶贝塞尔曲线的一段上的四个点
     * @param t 运行百分比
     * @returns 曲线速度(向量)
     */
    public static getVelocity(p: PointTuple3, t: number) {
        const [p0, p1, p2, p3] = p;
        const vx = -3 * p0.x * (1 - t) * (1 - t) + 3 * p1.x * (1 - t) * (1 - t) - 6 * p1.x * t * (1 - t) + 6 * p2.x * t * (1 - t) - 3 * p2.x * t * t + 3 * p3.x * t * t;
        const vy = -3 * p0.y * (1 - t) * (1 - t) + 3 * p1.y * (1 - t) * (1 - t) - 6 * p1.y * t * (1 - t) + 6 * p2.y * t * (1 - t) - 3 * p2.y * t * t + 3 * p3.y * t * t;
        return { vx, vy };
    }
    /**
     * 获取三阶贝塞尔曲线当前位置
     * @param p 三阶贝塞尔曲线的一段上的四个点
     * @param t 运行百分比
     * @returns 位置分量
     */
    public static getPosition(p: PointTuple3, t: number) {
        const [p0, p1, p2, p3] = p;
        const x = (1 - t) * (1 - t) * (1 - t) * p0.x + 3 * t * (1 - t) * (1 - t) * p1.x + 3 * t * t * (1 - t) * p2.x + t * t * t * p3.x;
        const y = (1 - t) * (1 - t) * (1 - t) * p0.y + 3 * t * (1 - t) * (1 - t) * p1.y + 3 * t * t * (1 - t) * p2.y + t * t * t * p3.y;
        return { x, y };
    }
    /**
     * 获取三阶贝塞尔曲线某位置的方向角度[-PI, PI]
     * @param p 三阶贝塞尔曲线的一段上的四个点
     * @param t 运行百分比
     * @returns 方向角度
     */
    public static getDirection(p: PointTuple3, t: number) {
        const [p0, p1, p2, p3] = p;
        const p01 = this.getPositionFirstOrder([p0, p1], t);
        const p12 = this.getPositionFirstOrder([p1, p2], t);
        const p23 = this.getPositionFirstOrder([p2, p3], t);
        const p012 = this.getPositionFirstOrder([p01, p12], t);
        const p123 = this.getPositionFirstOrder([p12, p23], t);
        const angle = Math.atan2(p123.y - p012.y, p123.x - p012.x);
        return angle;
    }
    /**
     * 获取一阶贝塞尔曲线当前位置
     * @param p 一阶贝塞尔曲线的一段上的两个点
     * @param t 运行百分比
     * @returns 位置分量
     */
    public static getPositionFirstOrder(p: PointTuple1, t: number) {
        const [p0, p1] = p;
        return { x: (1 - t) * p0.x + t * p1.x, y: (1 - t) * p0.y + t * p1.y };
    }
}
